iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
Modern Web

TypeScript 魔法 - 喚醒你的程式碼靈感系列 第 23 篇

Day23 - 建立物件藍圖 - 類別(class)

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20231008/20152047YWryjPYPCe.png

什麼是 OOP?

OOP(Object-Oriented Programming,物件導向程式設計)是一種程式設計範式或方法論,以物件為核心,將資料和與資料相關的操作封裝在一起,以建立可重複使用和易於維護的程式碼。在物件導向程式設計中,程式被組織為一組相互關聯的物件,每個物件代表現實世界中的一個實體,具有屬性和方法。

什麼是類別?

類別是物件導向程式設計(Object-Oriented Programming,OOP)的核心概念之一,它提供了一種建立物件的藍圖。類別定義了物件的屬性和方法,允許我們將相關的程式碼組織在一起,並建立具有相似特性的多個物件。在 TypeScript 中,類別是一種強型別的結構,可以明確指定屬性和方法的型別。

如何定義一個類別

在 TypeScript 中,要定義一個類別,我們使用 class 關鍵字,後面跟著類別名稱。

class Department {
  // 屬性
  name: string;

  // 建構函式
  constructor(name: string) {
    this.name = name;
  }

  // 方法
  describe(this: Department) {
    console.log(`部門:${this.name}`);
  }
}

如何建立物件

要建立類別的物件,我們使用 new 關鍵字,後面跟著類別名稱,並傳遞建構函式所需的參數。

class Department {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  describe() {
    console.log(`部門:${this.name}`);
  }
}

const engineering = new Department('工程部');

建構函式和 this 關鍵字

建構函式是類別的一部分,它在建立物件時被呼叫,用於初始化物件的屬性。在建構函式內部,我們使用 this 關鍵字來存取物件的屬性,並將參數的值分配給物件的對應屬性。

class Department {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  describe() {
    console.log(`部門:${this.name}`);
  }
}

const engineering = new Department('工程部');
engineering.describe(); // 部門:工程部

關於 this 指向

當我們有一個 personCopy 的新物件,只有 sayHello 方法並指向原本的 perosn 物件,看看有什麼變化。

class Department {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  describe() {
    console.log(`部門:${this.name}`);
  }
}

const engineering = new Department('工程部');
engineering.describe(); // 部門:工程部

const engineeringCopy = {
  describe: engineering.describe,
};
engineeringCopy.describe(); // 部門:undefined

此時,TypeScript 編譯並不會報錯,但在運行時,我們可以看到輸出的值是 undefined,這是因為 engineeringCopy.describe 的 this 是指向自己,它是一個偽物件,本身沒有 name 屬性,最後,我們得到的輸出是 undefined。

函式帶入 this 做一層防護

在 describe 方法中,使用 this 參數指定該方法應該被調用在屬於 Department 類別的物件上。這樣一來,在方法內我們可以安全存取 this 物件的屬性。

class Department {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  describe(this: Department) {
    console.log(`部門:${this.name}`);
  }
}

const engineering = new Department('工程部');
engineering.describe(); // 部門:工程部

// 自己的物件新增一個 name 屬性,否則 TypeScript 會報錯
const engineeringCopy = {
  name: '會計部',
  describe: engineering.describe,
};
engineeringCopy.describe(); // 部門:會計部

public、private 和 protected

public、private 和 protected 是 TypeScript 特有的功能,用於控制類別的屬性和方法可存取性。

public

屬性或方法為是公有的,表示可以從任何地方被存取,預設所有的屬性和方法都是 public,可以省略。

class Department {
  name: string;
  employees: string[] = [];

  constructor(name: string) {
    this.name = name;
  }

  addEmployee(employee: string) {
    this.employees.push(employee);
  }

  printEmployeeInformation() {
    console.log(this.employees.length); // 2
    console.log(this.employees); // ['肉鬆', '傑尼龜']
  }
}

const engineering = new Department('工程部');

engineering.addEmployee('肉鬆'); // 可以存取
engineering.addEmployee('傑尼龜'); // 可以存取

engineering.printEmployeeInformation(); // 可以存取

private

屬性或方法是私有的,表示只能在類別內部存取,無法從類別外部存取。

class Department {
  name: string;
  private employees: string[] = [];

  constructor(name: string) {
    this.name = name;
  }

  addEmployee(employee: string) {
    this.employees.push(employee);
  }

  printEmployeeInformation() {
    console.log(this.employees.length);
    console.log(this.employees);
  }
}

const engineering = new Department('工程部');

engineering.addEmployee('肉鬆'); // 可以存取
engineering.addEmployee('傑尼龜'); // 可以存取

engineering.employees[2] = '小明'; // TypeScript 報錯,'employees' 是私用屬性,只可從類別 'Department' 中存取

engineering.printEmployeeInformation(); // 可以存取

protected

屬性或方法是受保護的,表示只能在類別內部和子類別存取,無法從類別外部存取。

class Department {
  name: string;
  protected employees: string[] = [];

  constructor(name: string) {
    this.name = name;
  }

  addEmployee(employee: string) {
    this.employees.push(employee);
  }

  printEmployeeInformation() {
    console.log(this.employees.length);
    console.log(this.employees);
  }
}

class RDDepartment extends Department {
  constructor(name: string) {
    super(name);
  }

  addRD(employee: string) {
    // 因為 employees 是 protected,所以可以在子類中存取
    this.employees.push(employee);
  }
}

const rd = new RDDepartment('工程部');
rd.addEmployee('肉鬆'); // 可以存取
rd.addRD('傑尼龜'); // 可以存取

rd.employees[2] = '小明'; // TypeScript 報錯,'employees' 是受保護屬性,只可從類別 'Department' 及其子類別中存取

rd.printEmployeeInformation(); // 可以存取

繼承(extends)

在 TypeScript 中,我們可以使用 extends 關鍵字實現繼承,這允許子類別繼承父類別的屬性和方法。子類別可以使用 super 關鍵字來調用父類別的建構函式或方法。

class Department {
  name: string;
  employees: string[] = [];

  constructor(name: string) {
    this.name = name;
  }

  addEmployee(employee: string) {
    this.employees.push(employee);
  }

  printEmployeeInformation() {
    console.log(this.employees.length);
    console.log(this.employees);
  }
}

class RDDepartment extends Department {
  constructor(name: string, employee: string[]) {
    super(name); // // 使用 super 調用父類別的建構函式
    this.employees = employee
  }

  addRD(employee: string) {
    this.employees.push(employee);
  }
}

const rd = new RDDepartment('工程部', ['肉鬆', '傑尼龜']);

rd.printEmployeeInformation();

存取器(get、set)

在 TypeScript 中,我們可以使用 get 和 set 控制類別的屬性的讀取和賦值。

class Person {

  constructor(name) {
    this.name = name;
  }

  get name() {
    return '小明';
  }

  set name(value) {
    console.log('setter: ' + value);
  }
}

let person = new Person('肉鬆'); // setter: 肉鬆
person.name = '傑尼龜'; // setter: 傑尼龜
console.log(person.name); // 小明

靜態屬性和方法(static)

在 TypeScript 中,我們可以使用 static 關鍵字定義靜態屬性或方法。靜態屬性或方法不需要物件類別就可以存取屬性或方法。

靜態屬性

class Department {
  static year = 2023;

  constructor() {
    console.log(this.year); // TypeScript 報錯,屬性 'year' 不存在於類型 'Department' 上
    console.log(Department.year); // 2023
  }
}

console.log(Department.year); // 2023

靜態方法

class Department {
  static createEmployee(name: string) {
    return {
      name,
    };
  }
}

const employee = Department.createEmployee('肉鬆'); // 可以直接調用靜態方法
console.log(employee); // {name: '肉鬆'}

抽象屬性和方法(abstract)

在 TypeScript 中,我們可以使用 abstract 關鍵字定義抽象屬性和方法。抽象類別是一個不能被物件化的類別,通常用來定義一個共同的介面,而具體實現留給子類別。

abstract class Department {
  static year: number = 2023;

  constructor() {
    console.log(Department.year); // 2023
  }

  // 抽象類別中的抽象方法,需要在子類別中實現
  abstract getEmployeeInfo(name: string): void;
}

class RDDepartment extends Department {
  getEmployeeInfo(name: string) {
    return {
      name,
      yearJoined: Department.year,
    };
  }
}

class ITDepartment extends Department {
  getEmployeeInfo(name: string) {
    return {
      name,
      yearJoined: Department.year,
    };
  }
}

const rd = new RDDepartment();
console.log(rd.getEmployeeInfo('肉鬆')); // {name: '肉鬆', yearJoined: 2023}

const it = new ITDepartment();
console.log(it.getEmployeeInfo('傑尼龜')); // {name: '傑尼龜', yearJoined: 2023}

本日重點

  1. 使用 class 關鍵字來定義一個類別。
  2. 使用 constructor 方法來初始化類別的屬性,這是在建立類別自動調用的特殊方法。
  3. 使用 this 參數指定該方法應該被調用在屬於哪個類別的物件上
  4. public 表示可以從任何地方被存取、private 表示只能在類別內部存取,無法從類別外部存取、protected 表示只能在類別內部和子類別存取,無法從類別外部存取。
  5. 使用 extends 關鍵字實現繼承,子類別可以使用 super 關鍵字來調用父類別的建構函式或方法。
  6. 使用 get 和 set 控制類別的屬性的讀取和賦值。
  7. 使用 static 關鍵字定義靜態屬性或方法,需要物件類別就可以存取屬性或方法。
  8. 使用 abstract 關鍵字定義抽象屬性和方法,具體需要在子類別實現。

參考


上一篇
Day22 - 確保資料完整性 - 空值合併運算子
下一篇
Day24 - 實作介面與類別
系列文
TypeScript 魔法 - 喚醒你的程式碼靈感 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言